package simpleftp.client.control;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import simpleftp.client.gui.FTPClientWindow;
import simpleftp.client.gui.ProgressBar;
import simpleftp.server.Downloader;
import simpleftp.server.FTPServer;

public class CommandDispatcher implements Runnable 
{
	public static final String NL = "\n";
	private static final String USER = "USER";
	private static final String PASS = "PASS";
	private static final String SERVER = "SERVER";
	private static final String PORT = "PORT";
	private static final String SETTINGS_ROOT = "simpleftpsettings";
	private static final String DEFAULT_SETTINGS = SETTINGS_ROOT + "/default";
	private static final String LAST_SETTINGS = SETTINGS_ROOT + "/last";
	private static final String DEF_USER = "paris";
	private static final String DEF_PASS = "paris";
	private static final String DEF_SERVER = "127.0.0.1";
	private static final String DEF_PORT = "7802";
	private final ExecutorService executorPool;
	private final CompletionService<Boolean> completionPool;
	private final FTPClientWindow window;
	private final int numberOfCores;
	private final double blockingCoefficient = 0.9;
	private final int poolSize;
	private BlockingQueue<Command> commands;
	private SimpleFTP client;
	private Preferences userPrefs;
	private String server;
	private String port;
	private String user;
	private String pass;
	private boolean alreadyConnected;

	public CommandDispatcher(BlockingQueue<Command> commandQueue, FTPClientWindow window) {
		this.commands = commandQueue;
		this.window = window;
		alreadyConnected = false;
		numberOfCores = Runtime.getRuntime().availableProcessors();
		poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
		executorPool = Executors.newFixedThreadPool(poolSize);
		completionPool = new ExecutorCompletionService<Boolean>(executorPool);
		client = new SimpleFTP();
	}

	@Override
	public void run() {
		try {
			loadStartupPreferences();
			Command cmd = this.commands.take();
			while (!cmd.equals(Command.QUIT)) {
				switch (cmd) {
				case CONNECT:
					FTPClientWindow.progressBar.setVisible(false);
					connect();
					break;
				case DISCONNECT:
					FTPClientWindow.progressBar.setVisible(false);
					disconnect();
					break;
				case STOR:
					upload();
					break;
				case LIST:
					FTPClientWindow.progressBar.setVisible(false);
					listDir();
					break;
				case RETR:
					try {
						download();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					break;
				case PWD:
					FTPClientWindow.progressBar.setVisible(false);
					pwd();
					break;
				case CWD:
					FTPClientWindow.progressBar.setVisible(false);
					cwd();
					break;
				case CLEAR:
					FTPClientWindow.progressBar.setVisible(false);
					clearConsole();
					break;
				case LOADPREFS:
					loadPreferences();
					break;
				case SAVEPREFS:
					savePreferences();
					break;
				case DELPREF:
					deletePreference();
					break;
				default:
					break;
				}
				
				//FTPClientWindow.progressBar.setVisible(false);
				
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
				
				cmd = this.commands.take();
				
			}
			saveLastPreferences();
			disconnect();
			executorPool.shutdown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void clearConsole() {
		window.console.setText("");
	}

	private void saveLastPreferences() 
	{
		userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
		userPrefs.put(USER, window.tfusername.getText());
		userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
		userPrefs.put(SERVER, window.tfservername.getText());
		userPrefs.put(PORT, window.tfport.getText());
		try {
			userPrefs.flush();
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void loadStartupPreferences() 
	{
		try {
			userPrefs = Preferences.userRoot();
			if (!userPrefs.nodeExists(DEFAULT_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(DEFAULT_SETTINGS);
				userPrefs.put(USER, DEF_USER);
				userPrefs.put(PASS, DEF_PASS);
				userPrefs.put(SERVER, DEF_SERVER);
				userPrefs.put(PORT, DEF_PORT);
				userPrefs.flush();
			}
			if (userPrefs.nodeExists(LAST_SETTINGS)) {
				userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
			} 
			window.tfusername.setText(userPrefs.get(USER, DEF_USER));
			window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
			window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
			window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
		} catch (BackingStoreException e) 
		{
			e.printStackTrace();
		}
	}
	
	private void loadPreferences() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to load:", "Load Preferences",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				window.tfusername.setText(userPrefs.get(USER, DEF_USER));
				window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
				window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
				window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
				
				window.console.append(NL + "User preferences " + s + " loaded.");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
			} else {
				window.console.append(NL + "Preferences loading operation cancelled.");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}

	private void savePreferences() {
		String s = (String) JOptionPane.showInputDialog(window,
				"Save the current connection info as:", "Save Preferences",
				JOptionPane.QUESTION_MESSAGE, null, null, null);
		if ((s != null) && (s.length() > 0)) {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
			userPrefs.put(USER, window.tfusername.getText());
			userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
			userPrefs.put(SERVER, window.tfservername.getText());
			userPrefs.put(PORT, window.tfport.getText());
			try {
				userPrefs.flush();
			} catch (BackingStoreException e) {
				window.console.append(NL + "Could not save preferences: "
						+ e.getMessage());
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
				e.printStackTrace();
			}
			window.console.append(NL + "Preferences saved as " + s);
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
		} else {
			window.console.append(NL + "Preferences saving operation cancelled.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
		}
	}
	
	private void deletePreference() {
		try {
			userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
			String[] userSavedPrefs = userPrefs.childrenNames();
			String s = (String) JOptionPane.showInputDialog(window,
					"Choose preference to delete:", "Delete Preference",
					JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
			if ((s != null) && (s.length() > 0)) {
				userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
				userPrefs.removeNode();
				userPrefs.flush();
				window.console.append(NL + "User preferences " + s + " deleted.");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
			} else {
				window.console.append(NL + "Preferences deletion operation cancelled.");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
			}
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}
	}
	
	private void download() throws Exception 
	{
		//window.console.append(NL + "Command not yet supported");
		//throw new UnsupportedOperationException("Download operation to be implemented...");
		
		//Return to the path of the project
		String resumePath= new File("").getAbsolutePath();
		client.cwd(resumePath);
		//start progress bar
		FTPClientWindow.progressBar.setVisible(true);
		ProgressBar.setProgress(2, "Download... verify connection");
		
		//verify if user is connected
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			ProgressBar.setProgress(2, "Download... no connection, aborted.");
			return;
		}
		
		//if the user has not selected any file
		if(window.tree.getSelectionCount()==0){
			window.console.append(NL + "In the tree, select the file to download.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			ProgressBar.setProgress(2, "Download... no file selected, aborted.");
			return;
		}
		
		ProgressBar.setProgress(10, "Download... Get selected files.");
		
		//get the paths of all selected values
		TreePath[] paths = new TreePath[window.tree.getSelectionCount()];
		paths = window.tree.getSelectionPaths();
		//create a list of callable
		List<Future<Boolean>> downloaders = new ArrayList<Future<Boolean>>(
				window.tree.getSelectionCount());
		
		//System.out.println("Nb selected file : " + window.tree.getSelectionCount());
		
		ProgressBar.setProgress(50, "Download... Get paths.");
		
		//for each path, create a new file and download it
		for(TreePath path : paths){
			
			//System.out.println("selected file : " + path.toString());
			
			ProgressBar.setProgress(60, "Download... " + path.getLastPathComponent().toString());
			
			//get path of the project
			//String dir = new File(path.getLastPathComponent().toString()).getAbsolutePath().replace(path.getLastPathComponent().toString(), "");
			String dir = System.getProperty("user.dir");
			
			//System.out.println("dir : "+dir);
			
			//get path with subfolders
			String dir2 = path.toString().replace("[","").replace("]", "").replace(", ", "\\"); 
			//String dir3 = dir2.replace("The FTP Server\\SimpleFTP-Student-Goasguen-Segaud\\", "");
			String dir3 = dir2.replace(window.root.toString()+ "\\" + window.root.getFirstChild().toString() + "\\", "");
			
			File file = new File(dir+"\\"+dir3);
			
			//existing file
			if(file.exists()){
				//non a directory
				if(!file.isDirectory()){
					
					ProgressBar.setProgress(70, "Download... Start.");
					
					Downloader downloader = new Downloader(file, window, server, port, user,pass);
					//completionPool.submit(downloader);
					downloaders.add(completionPool.submit(downloader));

					//ProgressBar.setProgress(100, "Download... File(s) transfered.");
					
				}else{
					window.console.append(NL + "You can not download a directory. Please select a file.");
					//The pane auto-scrolls
					window.console.setCaretPosition(window.console.getDocument().getLength());
				}
			}
			else{
				throw new Exception("Download: file does not exist");
			}
			ProgressBar.setProgress(100, "Download... File(s) transfered.");
		}
		
		//FTPClientWindow.progressBar.dispose();
	}

	private void upload() 
	{
		
		FTPClientWindow.progressBar.setVisible(true);
		ProgressBar.setProgress(2, "Upload... verify connection");
		
		if (!alreadyConnected) {
			window.console.append(NL + "You are not connected to any server.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			return;
		}
		
		ProgressBar.setProgress(2, "Upload... choose file");
		
		final JFileChooser fc = new JFileChooser();
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fc.setMultiSelectionEnabled(true);
		fc.setDragEnabled(true);
		int returnVal = fc.showDialog(window, "Upload");
		
		ProgressBar.setProgress(20, "Upload... get files");
		
		if (returnVal == JFileChooser.APPROVE_OPTION) 
		{
			File[] files = fc.getSelectedFiles();
			List<Future<Boolean>> uploaders = new ArrayList<Future<Boolean>>(
					files.length);
			
			ProgressBar.setProgress(40, "Upload... Start");
			
			for (File file : files) {
				
				ProgressBar.setProgress(60, "Upload... " + file.getName());
				
				Uploader uploader = new Uploader(file, window, server, port, user,
						pass);
				uploaders.add(completionPool.submit(uploader));
			}
			
			ProgressBar.setProgress(100, "Upload... Files(s) transfered");
		} else 
		{
			window.console.append(NL + "Upload action cancelled.");
			ProgressBar.setProgress(0, "Upload... cancelled");
		}
	}

	private void listDir() 
	{
		if (!alreadyConnected) 
		{
			window.console.append(NL + "You are not connected to any server.");
			return;
		}
		try {
			String dirContent = client.ls();
			window.console.append(NL + dirContent);
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			
			// TODO complete / rewrite to get the file tree from the server - Complete
			window.root.removeAllChildren();
			
			/******JSON Method********/
			
			//We get the completeJsontree
			JSONObject jsonTree = client.getFileTree();
			
			//We get the complete file tree from the server 
			DefaultMutableTreeNode jsonUserRoot=new DefaultMutableTreeNode(jsonTree.get("name")); ;
			
			//System.out.println("1="+jsonTree.get("name")+"2="+jsonTree.get(2)+"3="+jsonTree.get(3));
			populateTreeFromJson(jsonUserRoot,jsonTree);
			
			//We add the tree to the root
			window.root.add(jsonUserRoot);
			
			/*************************************************/
			
			
			/*******DefaultMutableTreeNode Method************/
			
			/*//We get the complete tree
			DefaultMutableTreeNode userRoot = client.getFileTree();
			
			//We add the tree to the root
			window.root.add(userRoot);*/
			
			/**************************************************/
			
			
			/*********Previous method without getting the complete tree**************/
			/*File f = client.getFileTree();
			DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(f.getName());
			System.out.println("userRoot : "+userRoot.toString());
			System.out.println("file : "+f.getName());
			populateTree(userRoot, f);
			window.root.add(userRoot);*/
			/**************************************************/
			
			
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			e.printStackTrace();
		}

	}
	
	private void populateTreeFromJson(DefaultMutableTreeNode jsonUserRoot, JSONObject root) 
	{
		//Initialisation
		JSONArray children = (JSONArray) root.get("children");
		JSONObject child =null;
		DefaultMutableTreeNode rootchild =null;
		
		//For every child in the file
		for(int i=0;i<children.size();i++)
		{
			child = (JSONObject) children.get(i);
			
			//Create a new node from the name of the jsonobject
			rootchild= new DefaultMutableTreeNode(child.get("name").toString());
			
			//If the child is a directory repopulate from it
			if(child.get("type").equals("directory")==true) 
			{
				populateTreeFromJson(rootchild, child)  ;
			}
			
			//Add it to the tree
			jsonUserRoot.add(rootchild);
		}
	}
	
	/*private void populateTree(DefaultMutableTreeNode userRoot, File root) 
	{
		//Get all the files in the root
		File [] rootfiles = root.listFiles();
		
		//For every file in the root
		for(File f : rootfiles)
		{
			//Create a new node and add it to the tree whether it is a directory or not
			DefaultMutableTreeNode child = new DefaultMutableTreeNode(f.getName());
			userRoot.add(child);
			//If the file is a directory redo the populateTree for this directory as the root
			if(f.isDirectory()==true) populateTree(child, f);		
		}
	}*/

	private void pwd() {
		try {
			if (!alreadyConnected) {
				window.console.append(NL + "You are not connected to any server.");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
				return;
			}
			String curDir = client.pwd();
			window.console.append(NL + "Current directory on FTP server: " + curDir);
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			e.printStackTrace();
		}
	}
	
	private void cwd() {
			
			if (!alreadyConnected) {
				window.console.append(NL + "You are not connected to any server.");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
				return;
			}
			//Get the currentDir with absolute path
			//String currentDir = new File(window.tree.getSelectionPath().getLastPathComponent().toString()).getAbsolutePath();
			
			//default (root)
			String currentDir = System.getProperty("user.dir");
			//if items selected 
			if(window.tree.getSelectionCount()>0){
				String dir = System.getProperty("user.dir");
				//get path with subfolders
				String dir2 = window.tree.getSelectionPath().toString().replace("[","").replace("]", "").replace(", ", "\\"); 
				//String dir3 = dir2.replace("The FTP Server\\SimpleFTP-Student-Goasguen-Segaud\\", "");
				String dir3 = dir2.replace(window.root.toString()+ "\\" + window.root.getFirstChild().toString() + "\\", "");
				
				//System.out.println("replace : " + window.root.toString() + " " + window.root.getFirstChild().toString());
				
				currentDir = dir + "\\" + dir3;
			}
				
			try {
				//Get path from the project root
				//boolean cwdSuccess=client.cwd(window.tree.getSelectionPath().toString().replace("[","").replace("]", "").replace(", ", "\\"));
				
				//Call cwd from the client with the currentDir in parameter
				boolean cwdSuccess=client.cwd(currentDir);
				
				if (cwdSuccess){
					window.console.append(NL + "cwd succeed: " + currentDir);
					//The pane auto-scrolls
					window.console.setCaretPosition(window.console.getDocument().getLength());
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//Change the root to the current directory
			//window.root.setParent(new DefaultMutableTreeNode(""););
	}

	private void disconnect() {
		try {
			if (alreadyConnected) {
				client.disconnect();
				alreadyConnected = false;
				window.console.append(NL + "Now disconnected");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
			} else {
				window.console.append(NL + "Already disconnected");
				//The pane auto-scrolls
				window.console.setCaretPosition(window.console.getDocument().getLength());
			}
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			e.printStackTrace();
		}

	}

	private void connect() {
		if (alreadyConnected) {
			window.console.append(NL
					+ "You are already connected to this server.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			return;
		}
		server = window.tfservername.getText();
		if (server == null || "".equals(server)) {
			window.console.append(NL+ "You must specify the server IP address.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			return;
		}
		port = window.tfport.getText();
		if (port == null || "".equals(port)) {
			window.console.append(NL+ "You must specify the server port I must connect to.");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			return;
		}
		user = window.tfusername.getText();
		if (user == null || "".equals(user))
			user = "anonymous";
		pass = new String(window.pfuserpass.getPassword());
		if (pass == null || "".equals(pass))
			pass = "anonymous";
		try {
			client.connect(server, Integer.parseInt(port), user, pass);
			alreadyConnected = true;
			window.console.append(NL + "Now connected");
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			pwd();
			listDir();
		} catch (IOException e) {
			window.console.append(NL + e.getMessage());
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			e.printStackTrace();
		} catch (NumberFormatException nfe) {
			window.console.append(NL + nfe.getMessage());
			//The pane auto-scrolls
			window.console.setCaretPosition(window.console.getDocument().getLength());
			nfe.printStackTrace();
		}
	}
}
